Skip to content

Add gumption-api Cloud Run proxy (Option A: Firebase ID-token auth)#28

Draft
fsu9913-gif wants to merge 2 commits into
mainfrom
cursor/gumption-api-proxy-d91f
Draft

Add gumption-api Cloud Run proxy (Option A: Firebase ID-token auth)#28
fsu9913-gif wants to merge 2 commits into
mainfrom
cursor/gumption-api-proxy-d91f

Conversation

@fsu9913-gif
Copy link
Copy Markdown
Contributor

Why

Implements the hosting-plan API proxy chosen as Option A: auth is enforced inside the service so the Vite app can call Cloud Run directly from the browser without losing the security guarantee.

Network edge  →  --allow-unauthenticated   (no IAM round-trip from the browser)
App layer     →  requireFirebaseAuth       (verifyIdToken via firebase-admin)
                 enforceDailyCap           (Firestore-backed per-user spend cap)

Provider keys live in Secret Manager and are injected at deploy time. Vertex AI Gemini uses ADC from the runtime SA — no key for Gemini at all; calls bill straight against project credits.

Routes

All /v1/* require Authorization: Bearer <firebase-id-token>.

Method + path Upstream Auth model
GET /healthz public
POST /v1/anthropic/messages api.anthropic.com Secret Manager anthropic-key
POST /v1/openai/chat/completions api.openai.com Secret Manager openai-key
POST /v1/xai/chat/completions api.x.ai Secret Manager xai-key
POST /v1/gemini/:model:generateContent {region}-aiplatform.googleapis.com ADC from runtime SA

What ships

  • the-unit/api/ — Express + TypeScript service, ESM, Node 20.
  • the-unit/api/Dockerfile — multi-stage node:20-alpine.
  • the-unit/api/test/boot.test.ts — offline boot tests (no GCP creds required).
  • the-unit/scripts/setup-secrets.sh — one-time SA + IAM + Secret Manager bootstrap.
  • the-unit/scripts/deploy-gcp.sh + deploy-gcp.ps1gcloud run deploy --source.
  • .github/workflows/the-unit-api-deploy.yml — auto-deploy on push to main.

Defaults baked in (override via env vars)

Var Value Why
PROJECT_ID temporal-frame-494501-p5 Consolidate with Silverback GIA2; one bill, one IAM model, one audit trail.
REGION us-central1 Cheapest Vertex region; full model coverage.
SERVICE gumption-api
Runtime SA gumption-api@<project>.iam.gserviceaccount.com Roles: aiplatform.user, secretmanager.secretAccessor, datastore.user, logging.logWriter. No editor/owner.
DAILY_TOKEN_CAP 250000 Per-user, per-day. Adjust per route later if needed.

How to deploy

export PROJECT_ID=temporal-frame-494501-p5
export ANTHROPIC_API_KEY=sk-ant-... OPENAI_API_KEY=sk-... XAI_API_KEY=xai-...

bash the-unit/scripts/setup-secrets.sh     # one-time
bash the-unit/scripts/deploy-gcp.sh        # every deploy (or let GitHub Actions do it)

GitHub Actions deploys auto-trigger on the-unit/api/** pushes to main, but require two repo secrets:

  • GCP_WORKLOAD_IDENTITY_PROVIDER — workload identity provider resource name.
  • GCP_DEPLOY_SA — deploy service account email (separate from the runtime SA).

Testing

Boot tests run the actual Express app with the real auth middleware, so the 401 paths are end-to-end through firebase-admin.verifyIdToken():

✅ npm install                                  280 packages, 0 high/critical CVEs
✅ npm run typecheck                            tsc --noEmit, clean
✅ npm run build                                tsc emits dist/
✅ npx tsx --test test/boot.test.ts             6/6 pass
   ├── GET /healthz                                                          200
   ├── POST /v1/openai/chat/completions             (no bearer)              401 missing_bearer_token
   ├── POST /v1/anthropic/messages                  (no bearer)              401 missing_bearer_token
   ├── POST /v1/gemini/gemini-2.0-flash-001:...     (no bearer)              401 missing_bearer_token
   ├── POST /v1/openai/chat/completions             (bogus bearer)           401 invalid_id_token
   └── GET /nope                                                             404 not_found

Full boot-test log: gumption_api_boot_tests.log

End-to-end testing against real Vertex / Anthropic / OpenAI / xAI upstreams will happen after deploy, since the agent VM has no GCP credentials for temporal-frame-494501-p5.

Not yet done (open follow-ups)

  • Set the billing budget cap in the project (Console → Billing → Budgets). Do this before the first deploy.
  • Rotate any provider keys that ever lived in browser localStorage before storing the rotated values in Secret Manager.
  • Reconcile Apigee — Samantha enabled apigee.googleapis.com 2 days ago; this proxy does not depend on it. If you're not using Apigee, run gcloud services disable apigee.googleapis.com --force to avoid drift.
  • Front-end client snippet (Vite app) that attaches the Firebase ID token to fetch calls — will land in a follow-up PR once web/ is pushed up from your laptop.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

cursoragent and others added 2 commits May 7, 2026 10:25
Adds the-unit/unit_alert.py: a small CLI that walks
the-unit/The_Unit_Assembly/Hub_*/ folders and prints a daily-dated
report of every .py / .txt file still pending review and finalization.

- Properly indented, py_compile-clean version of the user's draft
- Adds --dir flag to override the default Unit Assembly path
- Returns total pending count and ends with a one-action nudge
- Ships with three placeholder Hub folders (Lead Generation,
  CRM Automation, Content Engine) so the script runs out of the box
- README documents layout and usage

Co-authored-by: fsu9913-gif <fsu9913-gif@users.noreply.github.com>
Implements the hosting-plan API proxy with auth enforced inside the
service rather than at the network edge, so the Vite app can call it
directly from the browser without losing the security guarantee:

  Network edge   --allow-unauthenticated (no IAM round-trip)
  App layer      requireFirebaseAuth: verifyIdToken via firebase-admin
                 enforceDailyCap:     Firestore-backed per-user cap
                                      (gumption_usage/{uid}/days/{date}.tokens)

Routes (all require Authorization: Bearer <firebase-id-token>):
  POST /v1/anthropic/messages              -> api.anthropic.com
  POST /v1/openai/chat/completions         -> api.openai.com
  POST /v1/xai/chat/completions            -> api.x.ai
  POST /v1/gemini/:model:generateContent   -> Vertex AI (ADC, no API key)
  GET  /healthz                            -> public

Provider keys live in Secret Manager (anthropic-key, openai-key, xai-key)
and are injected into the Cloud Run service at deploy with --set-secrets.
Vertex Gemini uses ADC from the attached runtime SA, so it has no key at
all — calls bill against project credits.

Scaffolding:
  the-unit/api/                Express + TypeScript service
  the-unit/api/Dockerfile      Multi-stage node:20-alpine build
  the-unit/api/test/           Boot tests (offline; no GCP creds needed)
  the-unit/scripts/setup-secrets.sh    One-time SA + IAM + secrets bootstrap
  the-unit/scripts/deploy-gcp.sh       gcloud run deploy from source
  the-unit/scripts/deploy-gcp.ps1      PowerShell parity for Windows
  .github/workflows/the-unit-api-deploy.yml   Auto-deploy on push to main

Defaults (override via env vars in deploy scripts):
  PROJECT_ID  temporal-frame-494501-p5 (consolidate, no new project)
  REGION      us-central1              (cheapest Vertex region)
  SERVICE     gumption-api
  SA          gumption-api@temporal-frame-494501-p5.iam.gserviceaccount.com
              roles/aiplatform.user, roles/secretmanager.secretAccessor,
              roles/datastore.user, roles/logging.logWriter

Verified locally:
  - npm install (280 packages, 0 high/critical CVEs)
  - npm run typecheck (tsc --noEmit, clean)
  - npm run build (tsc emits dist/)
  - npx tsx --test test/boot.test.ts -> 6/6 pass
    * GET /healthz -> 200
    * POST /v1/openai|anthropic|gemini without bearer -> 401 missing_bearer_token
    * POST /v1/openai with bogus bearer -> 401 invalid_id_token
    * GET /nope -> 404

Co-authored-by: fsu9913-gif <fsu9913-gif@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 12, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
dmc-properties 1900e62 May 12 2026, 07:32 PM

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “gumption-api” Cloud Run proxy service under the-unit/api/ that’s intended to be called directly from a browser (edge unauthenticated) while enforcing Firebase ID-token auth and per-user token metering in the app layer. The PR also introduces a separate “THE UNIT — Daily Project Alert” local scanner and directory scaffolding under the-unit/.

Changes:

  • Introduce a new Express + TypeScript (Node 20, ESM) Cloud Run proxy with Firebase auth middleware, provider passthrough routes (Anthropic/OpenAI/xAI/Vertex Gemini), and Firestore-backed daily token accounting.
  • Add deployment/provisioning scripts plus a GitHub Actions workflow to deploy to Cloud Run using Workload Identity Federation.
  • Add a local productivity scanner (unit_alert.py) and hub folder scaffolding for The_Unit_Assembly/.

Reviewed changes

Copilot reviewed 21 out of 25 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
.github/workflows/the-unit-api-deploy.yml GitHub Actions workflow to authenticate to GCP and deploy the Cloud Run service on pushes to main.
the-unit/api/.dockerignore Excludes dev/test files and env/log files from Docker build context.
the-unit/api/.gitignore Ignores node_modules, build output, logs, and env files for the API package.
the-unit/api/Dockerfile Multi-stage Node 20 Alpine build for the API service.
the-unit/api/README.md Service documentation: routes, auth model, env vars, local dev, deploy pointers.
the-unit/api/package-lock.json Lockfile for the new API package dependencies.
the-unit/api/package.json Defines the new API package, scripts, and dependencies/devDependencies.
the-unit/api/src/auth.ts Firebase ID-token auth middleware using firebase-admin.
the-unit/api/src/index.ts Express app wiring: JSON parsing, logging, route mounting, healthz, 404 handler, main entrypoint.
the-unit/api/src/logger.ts Pino logger configuration.
the-unit/api/src/providers/anthropic.ts Anthropic /messages proxy route with usage parsing + token accounting.
the-unit/api/src/providers/gemini.ts Vertex AI Gemini proxy route using ADC via google-auth-library + token accounting.
the-unit/api/src/providers/openai.ts OpenAI Chat Completions proxy route with usage parsing + token accounting.
the-unit/api/src/providers/xai.ts xAI Chat Completions proxy route with usage parsing + token accounting.
the-unit/api/src/rateLimit.ts Firestore-backed daily token cap pre-check and best-effort post-call token recording.
the-unit/api/test/boot.test.ts Boot tests covering health, 401 paths, and 404 behavior.
the-unit/api/tsconfig.json TypeScript config for NodeNext ESM build output to dist/.
the-unit/README.md Documentation for the local “Daily Project Alert” scanner and expected directory layout.
the-unit/The_Unit_Assembly/Hub_Content_Engine/.gitkeep Keeps the Hub directory in git as part of the scaffold.
the-unit/The_Unit_Assembly/Hub_CRM_Automation/.gitkeep Keeps the Hub directory in git as part of the scaffold.
the-unit/The_Unit_Assembly/Hub_Lead_Generation/.gitkeep Keeps the Hub directory in git as part of the scaffold.
the-unit/scripts/deploy-gcp.ps1 PowerShell deploy script to Cloud Run (parity with bash deploy script).
the-unit/scripts/deploy-gcp.sh Bash deploy script to Cloud Run, sets env vars and wires Secret Manager secrets.
the-unit/scripts/setup-secrets.sh One-time provisioning for runtime SA roles + creating/updating Secret Manager secrets.
the-unit/unit_alert.py Local scanner script that lists pending .py/.txt files under hub folders.
Comments suppressed due to low confidence (1)

the-unit/api/src/rateLimit.ts:49

  • If the Firestore read fails, enforceDailyCap currently logs and calls next(), effectively disabling spend metering during outages/misconfig. For a cost-control guardrail, it’s safer to fail closed (e.g., return 503/429) or make fail-open behavior an explicit, configurable choice.
  } catch (err) {
    logger.error({ err: (err as Error).message, uid: req.uid }, "rate_limit_check_failed");
    next();
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread the-unit/api/src/index.ts
Comment on lines +12 to +25
const app = express();
app.disable("x-powered-by");
app.use(express.json({ limit: "2mb" }));
app.use(pinoHttp({ logger }));

app.get("/healthz", (_req, res) => {
res.json({ ok: true, service: "gumption-api" });
});

app.use("/v1/anthropic", requireFirebaseAuth, enforceDailyCap, anthropicRouter);
app.use("/v1/openai", requireFirebaseAuth, enforceDailyCap, openaiRouter);
app.use("/v1/xai", requireFirebaseAuth, enforceDailyCap, xaiRouter);
app.use("/v1/gemini", requireFirebaseAuth, enforceDailyCap, geminiRouter);

Comment on lines +18 to +45
/**
* Soft pre-check before sending the request to a provider. If the user has
* already exceeded the cap today, reject without making a paid LLM call.
*
* The post-call increment in `recordTokens` is what makes the cap real;
* this check only avoids spending money once a cap is already blown.
*/
export async function enforceDailyCap(
req: AuthedRequest,
res: Response,
next: NextFunction,
): Promise<void> {
if (!req.uid) {
res.status(401).json({ error: "missing_uid" });
return;
}
try {
const snap = await usageRef(req.uid).get();
const used = (snap.data()?.tokens as number | undefined) ?? 0;
if (used >= DAILY_TOKEN_CAP) {
res.status(429).json({
error: "daily_token_cap_exceeded",
used,
cap: DAILY_TOKEN_CAP,
});
return;
}
next();
Comment on lines +26 to +27
const text = await upstream.body.text();
res.status(upstream.statusCode).type("application/json").send(text);
Comment on lines +19 to +31
const upstream = await request("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": apiKey,
"anthropic-version": ANTHROPIC_VERSION,
"content-type": "application/json",
},
body: JSON.stringify(req.body),
});

const text = await upstream.body.text();
res.status(upstream.statusCode).type("application/json").send(text);

Comment on lines +17 to +28
const upstream = await request("https://api.x.ai/v1/chat/completions", {
method: "POST",
headers: {
authorization: `Bearer ${apiKey}`,
"content-type": "application/json",
},
body: JSON.stringify(req.body),
});

const text = await upstream.body.text();
res.status(upstream.statusCode).type("application/json").send(text);

Comment on lines +42 to +53
const upstream = await request(url, {
method: "POST",
headers: {
authorization: `Bearer ${accessToken}`,
"content-type": "application/json",
},
body: JSON.stringify(req.body),
});

const text = await upstream.body.text();
res.status(upstream.statusCode).type("application/json").send(text);

Comment thread the-unit/api/package.json
Comment on lines +17 to +26
"dependencies": {
"@google-cloud/aiplatform": "^3.34.0",
"@google-cloud/firestore": "^7.10.0",
"express": "^4.21.2",
"firebase-admin": "^12.7.0",
"google-auth-library": "^9.15.0",
"pino": "^9.5.0",
"pino-http": "^10.3.0",
"undici": "^6.21.0"
},
Comment thread the-unit/unit_alert.py
Comment on lines +1 to +11
#!/usr/bin/env python3
"""
THE UNIT — Daily Project Alert
Scans your local Hub directories under ``The_Unit_Assembly`` and prints a
daily list of code files (.py / .txt) that are still pending review and
finalization.

Usage:
python3 unit_alert.py
python3 unit_alert.py --dir /custom/path/to/The_Unit_Assembly
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants